/***************************************************************************************
 * Copyright (c) 2014-2024 Zihao Yu, Nanjing University
 *
 * NEMU is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *
 * See the Mulan PSL v2 for more details.
 ***************************************************************************************/

#include <common.h>
#if defined(CONFIG_ITRACE)
#include <dlfcn.h>
#include <capstone/capstone.h>

static size_t (*cs_disasm_dl)(csh handle, const uint8_t *code,
                              size_t code_size, uint64_t address, size_t count, cs_insn **insn);
static void (*cs_free_dl)(cs_insn *insn, size_t count);

static csh handle;

#if defined(__APPLE__)
#define CS_LIB_SUFFIX "5.dylib"
#elif defined(__linux__)
#define CS_LIB_SUFFIX "so.5"
#else
#error "Unsupported platform"
#endif

void init_disasm()
{
  void *dl_handle;
  dl_handle = dlopen("../nemu/tools/capstone/repo/libcapstone." CS_LIB_SUFFIX, RTLD_LAZY);
  assert(dl_handle);

  cs_err (*cs_open_dl)(cs_arch arch, cs_mode mode, csh *handle) = NULL;
  // cs_open_dl = dlsym(dl_handle, "cs_open");
  cs_open_dl = reinterpret_cast<
      cs_err (*)(cs_arch, cs_mode, csh *)>(dlsym(dl_handle, "cs_open"));
  assert(cs_open_dl);

  // cs_disasm_dl = dlsym(dl_handle, "cs_disasm");
  cs_disasm_dl = reinterpret_cast<
      size_t (*)(csh, const uint8_t *, size_t, uint64_t, size_t, cs_insn **)>(
      dlsym(dl_handle, "cs_disasm"));
  assert(cs_disasm_dl);

  // cs_free_dl = dlsym(dl_handle, "cs_free");
  cs_free_dl = reinterpret_cast<
      void (*)(cs_insn *, size_t)>(
      dlsym(dl_handle, "cs_free"));
  assert(cs_free_dl);

  cs_arch arch = CS_ARCH_RISCV;
  // cs_mode mode = static_cast<cs_mode>(CS_MODE_RISCV64 | CS_MODE_RISCVC);
  cs_mode mode = static_cast<cs_mode>(CS_MODE_RISCV32 | CS_MODE_RISCVC);
  int ret = cs_open_dl(arch, mode, &handle);
  assert(ret == CS_ERR_OK);

#ifdef CONFIG_ISA_x86
  cs_err (*cs_option_dl)(csh handle, cs_opt_type type, size_t value) = NULL;
  cs_option_dl = dlsym(dl_handle, "cs_option");
  assert(cs_option_dl);

  ret = cs_option_dl(handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT);
  assert(ret == CS_ERR_OK);
#endif
}

void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte)
{
  cs_insn *insn;
  size_t count = cs_disasm_dl(handle, code, nbyte, pc, 0, &insn);
  if (count != 1)
  {
    snprintf(str, size, "disasm error: %zu, code: 0x%x", count, *code);
    return;
  }
  int ret = snprintf(str, size, "%s", insn->mnemonic);
  if (insn->op_str[0] != '\0')
  {
    snprintf(str + ret, size - ret, "\t%s", insn->op_str);
  }
  cs_free_dl(insn, count);
}

#endif // CONFIG_ITRACE
